#region Header
// ------------------------ Licence / Copyright ------------------------
// 
// Copyright (c) 2008 Daniel Grunwald
// 
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
// 
// Author:
//  Daniel Grunwald
//
// ---------------------------------------------------------------------
#endregion

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;

namespace ScrumTable.Common.EventHandling
{
    /// <summary>
    /// The forwarder-generating code is in a separate class because it does not depend on type T.
    /// </summary>
    /// <remarks>
    /// See http://www.codeproject.com/KB/cs/WeakEvents.aspx
    /// </remarks>
	static class FastSmartWeakEventForwarderProvider
	{
        internal delegate bool ForwarderDelegate(WeakReference wr, object sender, EventArgs e);

        private static readonly Dictionary<MethodInfo, ForwarderDelegate> Forwarders = new Dictionary<MethodInfo, ForwarderDelegate>();
		private static readonly MethodInfo GetTarget = typeof(WeakReference).GetMethod("get_Target");
        private static readonly Type[] ForwarderParameters = { typeof(WeakReference), typeof(object), typeof(EventArgs) };

		internal static ForwarderDelegate GetForwarder(MethodInfo method)
		{
			lock (Forwarders) {
				ForwarderDelegate d;
				if (Forwarders.TryGetValue(method, out d))
					return d;
			}

            ForwarderDelegate fd = GenerateDelegate(method);

            lock (Forwarders) {
				Forwarders[method] = fd;
			}
			return fd;
		}

        private static ForwarderDelegate GenerateDelegate(MethodInfo method)
        {
            if (method.DeclaringType.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Length != 0)
                return GenerateDelegateWithReflection(method);
            return GenerateDelegateWithIL(method);
        }

        private static ForwarderDelegate GenerateDelegateWithReflection(MethodInfo method)
        {
            // ArgumentException -> cannot create weak event to anonymous method with closure thus call it with reflection
            return delegate(WeakReference wr, object sender, EventArgs e)
                       {
                           if (method.IsStatic)
                           {
                               method.Invoke(null, new[] {sender, e});
                           }
                           else
                           {
                               object target = wr.Target;
                               bool isAlive = wr.IsAlive;

                               if (isAlive)
                                   method.Invoke(target, new[] {sender, e});

                               return !isAlive;
                           }
                           return false;
                       };
        }

        private static ForwarderDelegate GenerateDelegateWithIL(MethodInfo method)
        {
            Debug.Assert(GetTarget != null);

            var parameters = method.GetParameters();

            DynamicMethod dm = new DynamicMethod(
                "FastSmartWeakEvent", typeof(bool), ForwarderParameters, method.DeclaringType);

            ILGenerator il = dm.GetILGenerator();

            if (!method.IsStatic)
            {
                il.Emit(OpCodes.Ldarg_0);
                il.EmitCall(OpCodes.Callvirt, GetTarget, null);
                il.Emit(OpCodes.Dup);
                Label label = il.DefineLabel();
                il.Emit(OpCodes.Brtrue, label);
                il.Emit(OpCodes.Pop);
                il.Emit(OpCodes.Ldc_I4_1);
                il.Emit(OpCodes.Ret);
                il.MarkLabel(label);
                // The castclass here is required for the generated code to be verifiable.
                // We can leave it out because we know this cast will always succeed
                // (the instance/method pair was taken from a delegate).
                // Unverifiable code is fine because private reflection is only allowed under FullTrust
                // anyways.
                //il.Emit(OpCodes.Castclass, method.DeclaringType);
            }
            il.Emit(OpCodes.Ldarg_1);
            il.Emit(OpCodes.Ldarg_2);
            // This castclass here is required to prevent creating a hole in the .NET type system.
            // See Program.TypeSafetyProblem in the 'SmartWeakEventBenchmark' to see the effect when
            // this cast is not used.
            // You can remove this cast if you trust add FastSmartWeakEvent.Raise callers to do
            // the right thing, but the small performance increase (about 5%) usually isn't worth the risk.
            il.Emit(OpCodes.Castclass, parameters[1].ParameterType);

            il.EmitCall(OpCodes.Call, method, null);
            il.Emit(OpCodes.Ldc_I4_0);
            il.Emit(OpCodes.Ret);
            return (ForwarderDelegate)dm.CreateDelegate(typeof(ForwarderDelegate));
        }
	}
}
